Micron Document
ZgRNS Git Node


Displaying Raw • Download

firmware/webmonitor.py HEAD (e0583c4d) Text, 12.32 KB

T8b949e"""
µReticulum — minimal HTTP monitor for a transport node
======================================================
A plain-HTTP dashboard on the LAN (NOT Reticulum) so you can point a browser at
the node and watch its routing tables, relay counters, known paths and live log
update in real time. Useful for operating a transport router.

GET / -> HTML dashboard (auto-refreshes every 2 s via /api)
GET /api -> JSON snapshot of the node's state

Path-table rows show a peer's announced display name (LXMF/NomadNet app_data)
next to its hash in the destination and via columns, plus a protocol pill
per destination (lxmf, lxmf-pn, nomad, voice-lxst, voice-mc, probe, or a
"?hex" tag for unknown apps) derived from the announce's name_hash. Names
live in RAM only and are relearned from announces after a reboot; protocol
labels are additionally backfilled from persisted identities.

Usage (from the router example, inside the asyncio loop):
import uasyncio as asyncio, webmonitor
asyncio.create_task(webmonitor.serve(node_name="my-router", port=80))

Then open http://<node-ip>/ from any device on the same network.
"""
Tff7b72import T7ee787gc
Tff7b72import T7ee787uasyncio Tff7b72as T7ee787asyncio
Tff7b72from T7ee787urnsT7ee787.T7ee787transport Tff7b72import Te6edf3Transport
Tff7b72from T7ee787urnsT7ee787.T7ee787identity Tff7b72import Te6edf3Identity
Tff7b72from T7ee787urns Tff7b72import Te6edf3const
Tff7b72from T7ee787urnsT7ee787.T7ee787log Tff7b72import Te6edf3get_log_ringTb4b4b4, Te6edf3logTb4b4b4, Te6edf3LOG_NOTICETb4b4b4, Te6edf3LOG_ERROR

Te6edf3_NODE Tff7b72= Ta5d6ff"Ta5d6ffuRNSTa5d6ff"
Te6edf3_BATTERY_FN Tff7b72= Tff7b72None T8b949e# set by serve(); returns battery volts (float) or None

T8b949e# Announced display names, RAM only — relearned from live announces after a
T8b949e# reboot (never persisted). Keyed by BOTH the announced destination hash and
T8b949e# the announcer's identity hash: path-table `via` entries hold a relay's
T8b949e# identity hash, so this is what lets the via column resolve to a name.
Te6edf3_NAMES Tff7b72= Tb4b4b4{Tb4b4b4} T8b949e# hash (bytes16) -> display name (str)
Te6edf3_PROTOS Tff7b72= Tb4b4b4{Tb4b4b4} T8b949e# dest hash (bytes16) -> protocol label (str)
Te6edf3_MAX_NAMES Tff7b72= T79c0ff64

T8b949e# App fingerprints: a destination hash is H(name_hash + identity_hash) and
T8b949e# every announce carries name_hash = H("app.aspects")[:10] in cleartext, so
T8b949e# known protocols are identified without any decryption. Unknown apps show
T8b949e# as "?" + the name_hash hex prefix (still groups them). Aspect sources:
T8b949e# LXMF/NomadNet reference, LXST Call.py ("lxst","call","endpoint"),
T8b949e# MeshChat meshchat.py ("call","audio").
Te6edf3_NH_LEN Tff7b72= Te6edf3constTff7b72.Td2a8ffNAME_HASH_LENGTH Tff7b72/Tff7b72/ T79c0ff8
Te6edf3_PROTO_TABLE Tff7b72= Tb4b4b4{Tb4b4b4}
Tff7b72for Te6edf3_nTb4b4b4, Te6edf3_l Tff7b72in Tb4b4b4(Tb4b4b4(Ta5d6ff"Ta5d6fflxmf.deliveryTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6fflxmfTa5d6ff"Tb4b4b4)Tb4b4b4,
Tb4b4b4(Ta5d6ff"Ta5d6fflxmf.propagationTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6fflxmf-pnTa5d6ff"Tb4b4b4)Tb4b4b4,
Tb4b4b4(Ta5d6ff"Ta5d6ffnomadnetwork.nodeTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ffnomadTa5d6ff"Tb4b4b4)Tb4b4b4,
Tb4b4b4(Ta5d6ff"Ta5d6fflxst.call.endpointTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ffvoice-lxstTa5d6ff"Tb4b4b4)Tb4b4b4,
Tb4b4b4(Ta5d6ff"Ta5d6ffcall.audioTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ffvoice-mcTa5d6ff"Tb4b4b4)Tb4b4b4,
Tb4b4b4(Ta5d6ff"Ta5d6ffurns.probeTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ffprobeTa5d6ff"Tb4b4b4)Tb4b4b4)Tb4b4b4:
Te6edf3_PROTO_TABLETb4b4b4[Te6edf3IdentityTff7b72.Td2a8fffull_hashTb4b4b4(Te6edf3_nTff7b72.Td2a8ffencodeTb4b4b4(Ta5d6ff"Ta5d6ffutf-8Ta5d6ff"Tb4b4b4)Tb4b4b4)Tb4b4b4[Tb4b4b4:Te6edf3_NH_LENTb4b4b4]Tb4b4b4] Tff7b72= Te6edf3_l
Tff7b72del Te6edf3_nTb4b4b4, Te6edf3_l


Tff7b72def Td2a8ff_putTb4b4b4(Te6edf3cacheTb4b4b4, Te6edf3hTb4b4b4, Te6edf3vTb4b4b4)Tb4b4b4:
Tff7b72if Te6edf3h Tff7b72not Tff7b72in Te6edf3cache Tff7b72and Tffa657lenTb4b4b4(Te6edf3cacheTb4b4b4) Tff7b72>Tff7b72= Te6edf3_MAX_NAMESTb4b4b4:
Te6edf3cacheTff7b72.Td2a8ffpopTb4b4b4(Tffa657nextTb4b4b4(Tffa657iterTb4b4b4(Te6edf3cacheTb4b4b4)Tb4b4b4)Tb4b4b4, Tff7b72NoneTb4b4b4)
Te6edf3cacheTb4b4b4[Te6edf3hTb4b4b4] Tff7b72= Te6edf3v


Tff7b72def Td2a8ff_note_nameTb4b4b4(Te6edf3hTb4b4b4, Te6edf3nameTb4b4b4)Tb4b4b4:
Te6edf3_putTb4b4b4(Te6edf3_NAMESTb4b4b4, Te6edf3hTb4b4b4, Te6edf3nameTb4b4b4)


Tff7b72def Td2a8ff_classifyTb4b4b4(Te6edf3destTb4b4b4)Tb4b4b4:
T8b949e"""Protocol label for a destination hash. Live announces fill _PROTOS via
_on_announce; after a reboot the cache is empty, so unlabeled entries are
recomputed from the persisted identity: H(candidate_name_hash + id_hash)
must equal the destination hash. Result (or a definitive miss) is cached."""
Te6edf3p Tff7b72= Te6edf3_PROTOSTff7b72.Td2a8ffgetTb4b4b4(Te6edf3destTb4b4b4)
Tff7b72if Te6edf3p Tff7b72is Tff7b72not Tff7b72NoneTb4b4b4:
Tff7b72return Te6edf3p
Te6edf3known Tff7b72= Tffa657getattrTb4b4b4(Te6edf3IdentityTb4b4b4, Ta5d6ff"Ta5d6ffknown_destinationsTa5d6ff"Tb4b4b4, Tff7b72NoneTb4b4b4)
Te6edf3data Tff7b72= Te6edf3knownTff7b72.Td2a8ffgetTb4b4b4(Te6edf3destTb4b4b4) Tff7b72if Te6edf3known Tff7b72else Tff7b72None
Tff7b72if Te6edf3data Tff7b72and Te6edf3dataTb4b4b4[T79c0ff2Tb4b4b4]Tb4b4b4:
Te6edf3id_hash Tff7b72= Te6edf3IdentityTff7b72.Td2a8fftruncated_hashTb4b4b4(Te6edf3dataTb4b4b4[T79c0ff2Tb4b4b4]Tb4b4b4)
Te6edf3label Tff7b72= Ta5d6ff"Ta5d6ff?Ta5d6ff"
Tff7b72for Te6edf3nh Tff7b72in Te6edf3_PROTO_TABLETb4b4b4:
Tff7b72if Te6edf3IdentityTff7b72.Td2a8fffull_hashTb4b4b4(Te6edf3nh Tff7b72+ Te6edf3id_hashTb4b4b4)Tb4b4b4[Tb4b4b4:Te6edf3constTff7b72.Td2a8ffTRUNCATED_HASHLENGTH Tff7b72/Tff7b72/ T79c0ff8Tb4b4b4] Tff7b72== Te6edf3destTb4b4b4:
Te6edf3label Tff7b72= Te6edf3_PROTO_TABLETb4b4b4[Te6edf3nhTb4b4b4]
Tff7b72break
Te6edf3_putTb4b4b4(Te6edf3_PROTOSTb4b4b4, Te6edf3destTb4b4b4, Te6edf3labelTb4b4b4)
Tff7b72return Te6edf3label
Tff7b72return Tff7b72None


Tff7b72def Td2a8ff_on_announceTb4b4b4(Te6edf3destTb4b4b4, Te6edf3app_dataTb4b4b4, Te6edf3packetTb4b4b4)Tb4b4b4:
T8b949e"""Transport announce hook: cache the protocol label (from the announce's
name_hash) and the announced display name (LXMF msgpack [name, ...] or
legacy raw utf-8, e.g. NomadNet node names)."""
Tff7b72tryTb4b4b4:
Te6edf3ks Tff7b72= Te6edf3constTff7b72.Td2a8ffKEYSIZE Tff7b72/Tff7b72/ T79c0ff8
Te6edf3nh Tff7b72= Tffa657bytesTb4b4b4(Te6edf3packetTff7b72.Td2a8ffdataTb4b4b4[Te6edf3ksTb4b4b4:Te6edf3ks Tff7b72+ Te6edf3_NH_LENTb4b4b4]Tb4b4b4)
Te6edf3_putTb4b4b4(Te6edf3_PROTOSTb4b4b4, Te6edf3destTb4b4b4, Te6edf3_PROTO_TABLETff7b72.Td2a8ffgetTb4b4b4(Te6edf3nhTb4b4b4) Tff7b72or Ta5d6ff"Ta5d6ff?Ta5d6ff" Tff7b72+ Te6edf3nhTff7b72.Td2a8ffhexTb4b4b4(Tb4b4b4)Tb4b4b4[Tb4b4b4:T79c0ff6Tb4b4b4]Tb4b4b4)
Tff7b72except Tf85149ExceptionTb4b4b4:
Tff7b72pass
Tff7b72from T7ee787urnsT7ee787.T7ee787lxmf Tff7b72import Te6edf3LXMRouter
Te6edf3name Tff7b72= Te6edf3LXMRouterTff7b72.Td2a8ff_parse_display_nameTb4b4b4(Te6edf3app_dataTb4b4b4) Tff7b72if Te6edf3app_data Tff7b72else Tff7b72None
Tff7b72if Tff7b72not Te6edf3nameTb4b4b4:
Tff7b72return
Te6edf3_note_nameTb4b4b4(Te6edf3destTb4b4b4, Te6edf3nameTb4b4b4[Tb4b4b4:T79c0ff32Tb4b4b4]Tb4b4b4)
Tff7b72tryTb4b4b4:
T8b949e# Announce data starts with the 64-byte public key -> identity hash.
Te6edf3_note_nameTb4b4b4(Te6edf3IdentityTff7b72.Td2a8fftruncated_hashTb4b4b4(
Te6edf3packetTff7b72.Td2a8ffdataTb4b4b4[Tb4b4b4:Te6edf3constTff7b72.Td2a8ffKEYSIZE Tff7b72/Tff7b72/ T79c0ff8Tb4b4b4]Tb4b4b4)Tb4b4b4, Te6edf3nameTb4b4b4[Tb4b4b4:T79c0ff32Tb4b4b4]Tb4b4b4)
Tff7b72except Tf85149ExceptionTb4b4b4:
Tff7b72pass


Te6edf3_PAGE Tff7b72= Ta5d6ff"""Ta5d6ff<!DOCTYPE html><html><head><meta charset=utf-8>
Ta5d6ff<meta name=viewport content=Ta5d6ff"Ta5d6ffwidth=device-width,initial-scale=1Ta5d6ff"Ta5d6ff>
Ta5d6ff<title>uRNS Transport Router</title><style>
Ta5d6ffbodyTa5d6ff{Ta5d6fffont-family:ui-monospace,monospace;background:#0c0e0c;color:#b8f5b8;margin:0;padding:1em}
Ta5d6ffh1Ta5d6ff{Ta5d6fffont-size:1.1em;color:#7fff7f;margin:.2em 0}h3Ta5d6ff{Ta5d6ffcolor:#6fce6f;margin:.8em 0 .3em;font-size:.95em}
Ta5d6ff.boxTa5d6ff{Ta5d6ffbackground:#121512;border:1px solid #243024;border-radius:6px;padding:.6em .8em;margin:.4em 0}
Ta5d6fftableTa5d6ff{Ta5d6ffborder-collapse:collapse;width:100Ta5d6ff%Ta5d6ff;font-size:.85em}td,thTa5d6ff{Ta5d6ffpadding:2px 8px;text-align:left;border-bottom:1px solid #1e271e}
Ta5d6ffthTa5d6ff{Ta5d6ffcolor:#6fce6f}bTa5d6ff{Ta5d6ffcolor:#7fff7f}.dimTa5d6ff{Ta5d6ffcolor:#5a6e5a}
Ta5d6ff#logTa5d6ff{Ta5d6ffbackground:#000;height:42vh;overflow:auto;white-space:pre-wrap;font-size:.8em;line-height:1.35em}
Ta5d6ff.noteTa5d6ff{Ta5d6ffcolor:#9fe89f}.errTa5d6ff{Ta5d6ffcolor:#ff6b6b}.warnTa5d6ff{Ta5d6ffcolor:#ffc14d}.critTa5d6ff{Ta5d6ffcolor:#ff6b6b}
Ta5d6ff.pillTa5d6ff{Ta5d6ffdisplay:inline-block;background:#1c241c;border:1px solid #2c3a2c;border-radius:10px;padding:1px 8px;margin:1px}
Ta5d6ff.onTa5d6ff{Ta5d6ffcolor:#7fff7f}.offTa5d6ff{Ta5d6ffcolor:#ff6b6b}
Ta5d6ff</style></head><body>
Ta5d6ff<h1>µReticulum Transport Router &mdash; <span id=node class=dim>...</span></h1>
Ta5d6ff<div class=box id=info>connecting...</div>
Ta5d6ff<h3>Tables &amp; relay counters</h3><div class=box id=tables></div>
Ta5d6ff<h3>Known paths</h3><div class=box id=paths></div>
Ta5d6ff<h3>Live log <span class=dim>(newest at bottom)</span></h3><div class=box id=log></div>
Ta5d6ff<script>
Ta5d6fffunction esc(s)Ta5d6ff{Ta5d6ffreturn (Ta5d6ff'Ta5d6ff'Ta5d6ff+s).replace(/&/g,Ta5d6ff'Ta5d6ff&amp;Ta5d6ff'Ta5d6ff).replace(/</g,Ta5d6ff'Ta5d6ff&lt;Ta5d6ff'Ta5d6ff)}
Ta5d6fffunction nh(n,h)Ta5d6ff{Ta5d6ffreturn n?Ta5d6ff'Ta5d6ff<b>Ta5d6ff'Ta5d6ff+esc(n)+Ta5d6ff'Ta5d6ff</b> <span class=dim>Ta5d6ff'Ta5d6ff+h+Ta5d6ff'Ta5d6ff</span>Ta5d6ff'Ta5d6ff:h}
Ta5d6ffasync function tick()Ta5d6ff{
Ta5d6ff tryTa5d6ff{
Ta5d6ff const d=await (await fetch(Ta5d6ff'Ta5d6ff/apiTa5d6ff'Ta5d6ff,Ta5d6ff{Ta5d6ffcache:Ta5d6ff'Ta5d6ffno-storeTa5d6ff'Ta5d6ff})).json();
Ta5d6ff node.textContent=d.node+Ta5d6ff'Ta5d6ff Ta5d6ff'Ta5d6ff+(d.transport_id||Ta5d6ff'Ta5d6ff'Ta5d6ff).slice(0,16);
Ta5d6ff info.innerHTML=Ta5d6ff'Ta5d6ff<span class=pill>transport Ta5d6ff'Ta5d6ff+(d.transport_enabled?Ta5d6ff'Ta5d6ff<span class=on>ON</span>Ta5d6ff'Ta5d6ff:Ta5d6ff'Ta5d6ff<span class=off>OFF</span>Ta5d6ff'Ta5d6ff)+Ta5d6ff'Ta5d6ff</span>Ta5d6ff'
Ta5d6ff +Ta5d6ff'Ta5d6ff<span class=pill>free Ta5d6ff'Ta5d6ff+((d.free_mem/1024)|0)+Ta5d6ff'Ta5d6ff KB</span>Ta5d6ff'
Ta5d6ff +(d.battery?Ta5d6ff'Ta5d6ff<span class=pill>batt <b>Ta5d6ff'Ta5d6ff+d.battery.v.toFixed(2)+Ta5d6ff'Ta5d6ff V</b> ~Ta5d6ff'Ta5d6ff+d.battery.pct+Ta5d6ff'Ta5d6ff%Ta5d6ff</span>Ta5d6ff'Ta5d6ff:Ta5d6ff'Ta5d6ff'Ta5d6ff)
Ta5d6ff +d.interfaces.map(i=>Ta5d6ff'Ta5d6ff<span class=pill>Ta5d6ff'Ta5d6ff+esc(i.name)+Ta5d6ff'Ta5d6ff Ta5d6ff'Ta5d6ff+(i.online?Ta5d6ff'Ta5d6ff<span class=on>&#10003;</span>Ta5d6ff'Ta5d6ff:Ta5d6ff'Ta5d6ff<span class=off>&#10007;</span>Ta5d6ff'Ta5d6ff)+Ta5d6ff'Ta5d6ff</span>Ta5d6ff'Ta5d6ff).join(Ta5d6ff'Ta5d6ff'Ta5d6ff);
Ta5d6ff const t=d.tables,r=d.relayed;
Ta5d6ff tables.innerHTML=Ta5d6ff'Ta5d6ff<span class=pill>paths Ta5d6ff'Ta5d6ff+t.paths+Ta5d6ff'Ta5d6ff</span><span class=pill>reachable Ta5d6ff'Ta5d6ff+t.reachable+Ta5d6ff'Ta5d6ff</span>Ta5d6ff'
Ta5d6ff +Ta5d6ff'Ta5d6ff<span class=pill>links Ta5d6ff'Ta5d6ff+t.links+Ta5d6ff'Ta5d6ff</span><span class=pill>reverse Ta5d6ff'Ta5d6ff+t.reverse+Ta5d6ff'Ta5d6ff</span>Ta5d6ff'
Ta5d6ff +Ta5d6ff'Ta5d6ff<span class=pill>cache Ta5d6ff'Ta5d6ff+t.cache+Ta5d6ff'Ta5d6ff</span><span class=pill>queued Ta5d6ff'Ta5d6ff+t.queued+Ta5d6ff'Ta5d6ff</span><br>Ta5d6ff'
Ta5d6ff +Ta5d6ff'Ta5d6ff<b>RELAYED</b> <span class=pill>announces Ta5d6ff'Ta5d6ff+r.announces+Ta5d6ff'Ta5d6ff</span><span class=pill>data Ta5d6ff'Ta5d6ff+r.data+Ta5d6ff'Ta5d6ff</span>Ta5d6ff'
Ta5d6ff +Ta5d6ff'Ta5d6ff<span class=pill>links Ta5d6ff'Ta5d6ff+r.links+Ta5d6ff'Ta5d6ff</span><span class=pill>proofs Ta5d6ff'Ta5d6ff+r.proofs+Ta5d6ff'Ta5d6ff</span>Ta5d6ff'Ta5d6ff;
Ta5d6ff paths.innerHTML=d.paths.length?(Ta5d6ff'Ta5d6ff<table><tr><th>destination</th><th>app</th><th>hops</th><th>via</th><th>interface</th></tr>Ta5d6ff'
Ta5d6ff +d.paths.map(p=>Ta5d6ff'Ta5d6ff<tr><td>Ta5d6ff'Ta5d6ff+nh(p.dname,p.dest.slice(0,20))+Ta5d6ff'Ta5d6ff</td><td>Ta5d6ff'Ta5d6ff+(p.proto?Ta5d6ff'Ta5d6ff<span class=pill>Ta5d6ff'Ta5d6ff+esc(p.proto)+Ta5d6ff'Ta5d6ff</span>Ta5d6ff'Ta5d6ff:Ta5d6ff'Ta5d6ff'Ta5d6ff)+Ta5d6ff'Ta5d6ff</td><td>Ta5d6ff'Ta5d6ff+p.hops+Ta5d6ff'Ta5d6ff</td><td>Ta5d6ff'Ta5d6ff+nh(p.vname,p.via)+Ta5d6ff'Ta5d6ff</td><td>Ta5d6ff'Ta5d6ff+esc(p.iface)+Ta5d6ff'Ta5d6ff</td></tr>Ta5d6ff'Ta5d6ff).join(Ta5d6ff'Ta5d6ff'Ta5d6ff)+Ta5d6ff'Ta5d6ff</table>Ta5d6ff'Ta5d6ff):Ta5d6ff'Ta5d6ff<span class=dim>(none yet)</span>Ta5d6ff'Ta5d6ff;
Ta5d6ff const atBottom=log.scrollTop+log.clientHeight>=log.scrollHeight-30;
Ta5d6ff log.innerHTML=d.log.map(l=>Ta5d6ff{Ta5d6ffconst c=l.indexOf(Ta5d6ff'Ta5d6ff[ERRTa5d6ff'Ta5d6ff)>0||l.indexOf(Ta5d6ff'Ta5d6ff[CRITTa5d6ff'Ta5d6ff)>0?Ta5d6ff'Ta5d6fferrTa5d6ff'Ta5d6ff:l.indexOf(Ta5d6ff'Ta5d6ff[WARNTa5d6ff'Ta5d6ff)>0?Ta5d6ff'Ta5d6ffwarnTa5d6ff'Ta5d6ff:Ta5d6ff'Ta5d6ffnoteTa5d6ff'Ta5d6ff;return Ta5d6ff'Ta5d6ff<span class=Ta5d6ff'Ta5d6ff+c+Ta5d6ff'Ta5d6ff>Ta5d6ff'Ta5d6ff+esc(l)+Ta5d6ff'Ta5d6ff</span>Ta5d6ff'Ta5d6ff;}).join(Ta5d6ff'Tffea00\\Ta5d6ffnTa5d6ff'Ta5d6ff);
Ta5d6ff if(atBottom)log.scrollTop=log.scrollHeight;
Ta5d6ff }catch(e)Ta5d6ff{Ta5d6ffinfo.innerHTML=Ta5d6ff'Ta5d6ff<span class=off>disconnected — retrying...</span>Ta5d6ff'Ta5d6ff;}
Ta5d6ff setTimeout(tick,2000);
Ta5d6ff}
Ta5d6fftick();
Ta5d6ff</script></body></html>Ta5d6ff"""Tff7b72.Td2a8ffencodeTb4b4b4(Tb4b4b4)


Tff7b72def Td2a8ff_lipo_pctTb4b4b4(Te6edf3vTb4b4b4)Tb4b4b4:
T8b949e"""Rough single-cell LiPo state-of-charge (%) from resting voltage — approximate."""
Te6edf3pts Tff7b72= Tb4b4b4(Tb4b4b4(T79c0ff3.30Tb4b4b4, T79c0ff0Tb4b4b4)Tb4b4b4, Tb4b4b4(T79c0ff3.60Tb4b4b4, T79c0ff10Tb4b4b4)Tb4b4b4, Tb4b4b4(T79c0ff3.70Tb4b4b4, T79c0ff25Tb4b4b4)Tb4b4b4, Tb4b4b4(T79c0ff3.80Tb4b4b4, T79c0ff50Tb4b4b4)Tb4b4b4,
Tb4b4b4(T79c0ff3.90Tb4b4b4, T79c0ff65Tb4b4b4)Tb4b4b4, Tb4b4b4(T79c0ff4.00Tb4b4b4, T79c0ff80Tb4b4b4)Tb4b4b4, Tb4b4b4(T79c0ff4.10Tb4b4b4, T79c0ff92Tb4b4b4)Tb4b4b4, Tb4b4b4(T79c0ff4.20Tb4b4b4, T79c0ff100Tb4b4b4)Tb4b4b4)
Tff7b72if Te6edf3v Tff7b72<Tff7b72= Te6edf3ptsTb4b4b4[T79c0ff0Tb4b4b4]Tb4b4b4[T79c0ff0Tb4b4b4]Tb4b4b4:
Tff7b72return T79c0ff0
Tff7b72if Te6edf3v Tff7b72>Tff7b72= Te6edf3ptsTb4b4b4[Tff7b72-T79c0ff1Tb4b4b4]Tb4b4b4[T79c0ff0Tb4b4b4]Tb4b4b4:
Tff7b72return T79c0ff100
Tff7b72for Tb4b4b4(Te6edf3v0Tb4b4b4, Te6edf3p0Tb4b4b4)Tb4b4b4, Tb4b4b4(Te6edf3v1Tb4b4b4, Te6edf3p1Tb4b4b4) Tff7b72in Tffa657zipTb4b4b4(Te6edf3ptsTb4b4b4, Te6edf3ptsTb4b4b4[T79c0ff1Tb4b4b4:Tb4b4b4]Tb4b4b4)Tb4b4b4:
Tff7b72if Te6edf3v Tff7b72< Te6edf3v1Tb4b4b4:
Tff7b72return Tffa657intTb4b4b4(Te6edf3p0 Tff7b72+ Tb4b4b4(Te6edf3p1 Tff7b72- Te6edf3p0Tb4b4b4) Tff7b72* Tb4b4b4(Te6edf3v Tff7b72- Te6edf3v0Tb4b4b4) Tff7b72/ Tb4b4b4(Te6edf3v1 Tff7b72- Te6edf3v0Tb4b4b4)Tb4b4b4)
Tff7b72return T79c0ff100


Tff7b72def Td2a8ff_snapshotTb4b4b4(Tb4b4b4)Tb4b4b4:
Te6edf3T Tff7b72= Te6edf3Transport
Te6edf3paths Tff7b72= Tb4b4b4[Tb4b4b4]
Tff7b72for Te6edf3destTb4b4b4, Te6edf3e Tff7b72in Tffa657listTb4b4b4(Te6edf3TTff7b72.Td2a8ffpath_tableTff7b72.Td2a8ffitemsTb4b4b4(Tb4b4b4)Tb4b4b4)Tb4b4b4[Tb4b4b4:T79c0ff48Tb4b4b4]Tb4b4b4:
Tff7b72tryTb4b4b4:
Te6edf3rif Tff7b72= Te6edf3eTb4b4b4[Te6edf3constTff7b72.Td2a8ffIDX_PT_RECV_IFTb4b4b4]
Te6edf3via Tff7b72= Te6edf3eTb4b4b4[Te6edf3constTff7b72.Td2a8ffIDX_PT_NEXT_HOPTb4b4b4]
Te6edf3p Tff7b72= Tb4b4b4{
Ta5d6ff"Ta5d6ffdestTa5d6ff"Tb4b4b4: Te6edf3destTff7b72.Td2a8ffhexTb4b4b4(Tb4b4b4)Tb4b4b4,
Ta5d6ff"Ta5d6ffhopsTa5d6ff"Tb4b4b4: Te6edf3eTb4b4b4[Te6edf3constTff7b72.Td2a8ffIDX_PT_HOPSTb4b4b4]Tb4b4b4,
Ta5d6ff"Ta5d6ffviaTa5d6ff"Tb4b4b4: Te6edf3viaTff7b72.Td2a8ffhexTb4b4b4(Tb4b4b4)Tb4b4b4[Tb4b4b4:T79c0ff8Tb4b4b4]Tb4b4b4,
Ta5d6ff"Ta5d6ffifaceTa5d6ff"Tb4b4b4: Tffa657strTb4b4b4(Te6edf3rifTb4b4b4) Tff7b72if Te6edf3rif Tff7b72is Tff7b72not Tff7b72None Tff7b72else Ta5d6ff"Ta5d6ff?Ta5d6ff"Tb4b4b4,
Tb4b4b4}
Te6edf3n Tff7b72= Te6edf3_NAMESTff7b72.Td2a8ffgetTb4b4b4(Te6edf3destTb4b4b4)
Tff7b72if Te6edf3nTb4b4b4:
Te6edf3pTb4b4b4[Ta5d6ff"Ta5d6ffdnameTa5d6ff"Tb4b4b4] Tff7b72= Te6edf3n
Te6edf3n Tff7b72= Te6edf3_NAMESTff7b72.Td2a8ffgetTb4b4b4(Te6edf3viaTb4b4b4)
Tff7b72if Te6edf3nTb4b4b4:
Te6edf3pTb4b4b4[Ta5d6ff"Ta5d6ffvnameTa5d6ff"Tb4b4b4] Tff7b72= Te6edf3n
Te6edf3pr Tff7b72= Te6edf3_classifyTb4b4b4(Te6edf3destTb4b4b4)
Tff7b72if Te6edf3prTb4b4b4:
Te6edf3pTb4b4b4[Ta5d6ff"Ta5d6ffprotoTa5d6ff"Tb4b4b4] Tff7b72= Te6edf3pr
Te6edf3pathsTff7b72.Td2a8ffappendTb4b4b4(Te6edf3pTb4b4b4)
Tff7b72except Tf85149ExceptionTb4b4b4:
Tff7b72pass
Te6edf3batt Tff7b72= Tff7b72None
Tff7b72if Te6edf3_BATTERY_FNTb4b4b4:
Tff7b72tryTb4b4b4:
Te6edf3v Tff7b72= Te6edf3_BATTERY_FNTb4b4b4(Tb4b4b4)
Tff7b72if Te6edf3v Tff7b72is Tff7b72not Tff7b72NoneTb4b4b4:
Te6edf3batt Tff7b72= Tb4b4b4{Ta5d6ff"Ta5d6ffvTa5d6ff"Tb4b4b4: Tffa657roundTb4b4b4(Te6edf3vTb4b4b4, T79c0ff2Tb4b4b4)Tb4b4b4, Ta5d6ff"Ta5d6ffpctTa5d6ff"Tb4b4b4: Te6edf3_lipo_pctTb4b4b4(Te6edf3vTb4b4b4)Tb4b4b4}
Tff7b72except Tf85149ExceptionTb4b4b4:
Tff7b72pass
Tff7b72return Tb4b4b4{
Ta5d6ff"Ta5d6ffnodeTa5d6ff"Tb4b4b4: Te6edf3_NODETb4b4b4,
Ta5d6ff"Ta5d6fftransport_idTa5d6ff"Tb4b4b4: Te6edf3TTff7b72.Td2a8ffidentityTff7b72.Td2a8ffhashTff7b72.Td2a8ffhexTb4b4b4(Tb4b4b4) Tff7b72if Te6edf3TTff7b72.Td2a8ffidentity Tff7b72else Tff7b72NoneTb4b4b4,
Ta5d6ff"Ta5d6fftransport_enabledTa5d6ff"Tb4b4b4: Tffa657boolTb4b4b4(Te6edf3TTff7b72.Td2a8fftransport_enabledTb4b4b4)Tb4b4b4,
Ta5d6ff"Ta5d6fffree_memTa5d6ff"Tb4b4b4: Te6edf3gcTff7b72.Td2a8ffmem_freeTb4b4b4(Tb4b4b4)Tb4b4b4,
Ta5d6ff"Ta5d6ffinterfacesTa5d6ff"Tb4b4b4: Tb4b4b4[Tb4b4b4{Ta5d6ff"Ta5d6ffnameTa5d6ff"Tb4b4b4: Te6edf3iTff7b72.Td2a8ffnameTb4b4b4, Ta5d6ff"Ta5d6ffonlineTa5d6ff"Tb4b4b4: Tffa657boolTb4b4b4(Te6edf3iTff7b72.Td2a8ffonlineTb4b4b4)Tb4b4b4} Tff7b72for Te6edf3i Tff7b72in Te6edf3TTff7b72.Td2a8ffinterfacesTb4b4b4]Tb4b4b4,
Ta5d6ff"Ta5d6fftablesTa5d6ff"Tb4b4b4: Tb4b4b4{
Ta5d6ff"Ta5d6ffpathsTa5d6ff"Tb4b4b4: Tffa657lenTb4b4b4(Te6edf3TTff7b72.Td2a8ffpath_tableTb4b4b4)Tb4b4b4, Ta5d6ff"Ta5d6ffreachableTa5d6ff"Tb4b4b4: Tffa657lenTb4b4b4(Te6edf3TTff7b72.Td2a8ffreachable_destinationsTb4b4b4)Tb4b4b4,
Ta5d6ff"Ta5d6fflinksTa5d6ff"Tb4b4b4: Tffa657lenTb4b4b4(Te6edf3TTff7b72.Td2a8fflink_tableTb4b4b4)Tb4b4b4, Ta5d6ff"Ta5d6ffreverseTa5d6ff"Tb4b4b4: Tffa657lenTb4b4b4(Te6edf3TTff7b72.Td2a8ffreverse_tableTb4b4b4)Tb4b4b4,
Ta5d6ff"Ta5d6ffcacheTa5d6ff"Tb4b4b4: Tffa657lenTb4b4b4(Te6edf3TTff7b72.Td2a8ffpacket_cacheTb4b4b4)Tb4b4b4, Ta5d6ff"Ta5d6ffqueuedTa5d6ff"Tb4b4b4: Tffa657lenTb4b4b4(Te6edf3TTff7b72.Td2a8ffannounce_tableTb4b4b4)Tb4b4b4,
Tb4b4b4}Tb4b4b4,
Ta5d6ff"Ta5d6ffrelayedTa5d6ff"Tb4b4b4: Tb4b4b4{
Ta5d6ff"Ta5d6ffannouncesTa5d6ff"Tb4b4b4: Te6edf3TTff7b72.Td2a8ffrelayed_announcesTb4b4b4, Ta5d6ff"Ta5d6ffdataTa5d6ff"Tb4b4b4: Te6edf3TTff7b72.Td2a8ffrelayed_dataTb4b4b4,
Ta5d6ff"Ta5d6fflinksTa5d6ff"Tb4b4b4: Te6edf3TTff7b72.Td2a8ffrelayed_linksTb4b4b4, Ta5d6ff"Ta5d6ffproofsTa5d6ff"Tb4b4b4: Te6edf3TTff7b72.Td2a8ffrelayed_proofsTb4b4b4,
Tb4b4b4}Tb4b4b4,
Ta5d6ff"Ta5d6ffpathsTa5d6ff"Tb4b4b4: Te6edf3pathsTb4b4b4,
Ta5d6ff"Ta5d6ffbatteryTa5d6ff"Tb4b4b4: Te6edf3battTb4b4b4,
Ta5d6ff"Ta5d6fflogTa5d6ff"Tb4b4b4: Tffa657listTb4b4b4(Te6edf3get_log_ringTb4b4b4(Tb4b4b4)Tb4b4b4)Tb4b4b4,
Tb4b4b4}


Tff7b72async Tff7b72def Td2a8ff_handleTb4b4b4(Te6edf3readerTb4b4b4, Te6edf3writerTb4b4b4)Tb4b4b4:
Tff7b72tryTb4b4b4:
Te6edf3req Tff7b72= Tff7b72await Te6edf3readerTff7b72.Td2a8ffreadlineTb4b4b4(Tb4b4b4)
T8b949e# Drain the rest of the request headers.
Tff7b72while Tff7b72TrueTb4b4b4:
Te6edf3h Tff7b72= Tff7b72await Te6edf3readerTff7b72.Td2a8ffreadlineTb4b4b4(Tb4b4b4)
Tff7b72if Tff7b72not Te6edf3h Tff7b72or Te6edf3h Tff7b72== Ta5d6ffbTa5d6ff"Tffea00\rTffea00\nTa5d6ff"Tb4b4b4:
Tff7b72break
Te6edf3path Tff7b72= Ta5d6ffbTa5d6ff"Ta5d6ff/Ta5d6ff"
Tff7b72tryTb4b4b4:
Te6edf3path Tff7b72= Te6edf3reqTff7b72.Td2a8ffsplitTb4b4b4(Ta5d6ffbTa5d6ff"Ta5d6ff Ta5d6ff"Tb4b4b4)Tb4b4b4[T79c0ff1Tb4b4b4]
Tff7b72except Tf85149ExceptionTb4b4b4:
Tff7b72pass

Tff7b72if Te6edf3pathTff7b72.Td2a8ffstartswithTb4b4b4(Ta5d6ffbTa5d6ff"Ta5d6ff/apiTa5d6ff"Tb4b4b4)Tb4b4b4:
Tff7b72import T7ee787json
Te6edf3body Tff7b72= Te6edf3jsonTff7b72.Td2a8ffdumpsTb4b4b4(Te6edf3_snapshotTb4b4b4(Tb4b4b4)Tb4b4b4)Tff7b72.Td2a8ffencodeTb4b4b4(Tb4b4b4)
Te6edf3ctype Tff7b72= Ta5d6ffbTa5d6ff"Ta5d6ffapplication/jsonTa5d6ff"
Tff7b72elseTb4b4b4:
Te6edf3body Tff7b72= Te6edf3_PAGE
Te6edf3ctype Tff7b72= Ta5d6ffbTa5d6ff"Ta5d6fftext/html; charset=utf-8Ta5d6ff"

Te6edf3writerTff7b72.Td2a8ffwriteTb4b4b4(Ta5d6ffbTa5d6ff"Ta5d6ffHTTP/1.0 200 OKTffea00\rTffea00\nTa5d6ffContent-Type: Ta5d6ff" Tff7b72+ Te6edf3ctype Tff7b72+
Ta5d6ffbTa5d6ff"Tffea00\rTffea00\nTa5d6ffConnection: closeTffea00\rTffea00\nTa5d6ffCache-Control: no-storeTffea00\rTffea00\nTa5d6ff"
Ta5d6ffbTa5d6ff"Ta5d6ffAccess-Control-Allow-Origin: *Tffea00\rTffea00\nTffea00\rTffea00\nTa5d6ff"Tb4b4b4)
Te6edf3writerTff7b72.Td2a8ffwriteTb4b4b4(Te6edf3bodyTb4b4b4)
Tff7b72await Te6edf3writerTff7b72.Td2a8ffdrainTb4b4b4(Tb4b4b4)
Tff7b72except Tf85149ExceptionTb4b4b4:
Tff7b72tryTb4b4b4:
Te6edf3writerTff7b72.Td2a8ffwriteTb4b4b4(Ta5d6ffbTa5d6ff"Ta5d6ffHTTP/1.0 500 Internal ErrorTffea00\rTffea00\nTa5d6ffConnection: closeTffea00\rTffea00\nTffea00\rTffea00\nTa5d6ff"Tb4b4b4)
Tff7b72await Te6edf3writerTff7b72.Td2a8ffdrainTb4b4b4(Tb4b4b4)
Tff7b72except Tf85149ExceptionTb4b4b4:
Tff7b72pass
Tff7b72finallyTb4b4b4:
Tff7b72tryTb4b4b4:
Te6edf3writerTff7b72.Td2a8ffcloseTb4b4b4(Tb4b4b4)
Tff7b72await Te6edf3writerTff7b72.Td2a8ffwait_closedTb4b4b4(Tb4b4b4)
Tff7b72except Tf85149ExceptionTb4b4b4:
Tff7b72pass
Te6edf3gcTff7b72.Td2a8ffcollectTb4b4b4(Tb4b4b4)


Tff7b72async Tff7b72def Td2a8ffserveTb4b4b4(Te6edf3node_nameTff7b72=Ta5d6ff"Ta5d6ffuRNSTa5d6ff"Tb4b4b4, Te6edf3hostTff7b72=Ta5d6ff"Ta5d6ff0.0.0.0Ta5d6ff"Tb4b4b4, Te6edf3portTff7b72=T79c0ff80Tb4b4b4, Te6edf3battery_fnTff7b72=Tff7b72NoneTb4b4b4)Tb4b4b4:
T8b949e"""Start the HTTP monitor and keep its task alive. Run as an asyncio task.

battery_fn: optional callable returning battery volts (float) or None; when
given, the value is shown as a gauge in the dashboard header."""
Tff7b72global Te6edf3_NODETb4b4b4, Te6edf3_BATTERY_FN
Te6edf3_NODE Tff7b72= Te6edf3node_name
Te6edf3_BATTERY_FN Tff7b72= Te6edf3battery_fn
Te6edf3TransportTff7b72.Td2a8ffregister_announce_handlerTb4b4b4(Te6edf3_on_announceTb4b4b4)
Tff7b72tryTb4b4b4:
Tff7b72await Te6edf3asyncioTff7b72.Td2a8ffstart_serverTb4b4b4(Te6edf3_handleTb4b4b4, Te6edf3hostTb4b4b4, Te6edf3portTb4b4b4)
Te6edf3logTb4b4b4(Ta5d6ff"Ta5d6ffWeb monitor listening on http://<node-ip>:Ta5d6ff" Tff7b72+ Tffa657strTb4b4b4(Te6edf3portTb4b4b4) Tff7b72+ Ta5d6ff"Ta5d6ff/ (LAN, plain HTTP)Ta5d6ff"Tb4b4b4, Te6edf3LOG_NOTICETb4b4b4)
Tff7b72except Tf85149Exception Tff7b72as Te6edf3eTb4b4b4:
Te6edf3logTb4b4b4(Ta5d6ff"Ta5d6ffWeb monitor failed to start: Ta5d6ff" Tff7b72+ Tffa657strTb4b4b4(Te6edf3eTb4b4b4)Tb4b4b4, Te6edf3LOG_ERRORTb4b4b4)
Tff7b72return
Tff7b72while Tff7b72TrueTb4b4b4:
Tff7b72await Te6edf3asyncioTff7b72.Td2a8ffsleepTb4b4b4(T79c0ff3600Tb4b4b4)

Served by rngit 1.5.2 - Generated in 0.06s